/**
 * MediaPipe 人体姿态检测服务
 * 
 * 文件概述：
 * 本文件提供基于 MediaPipe Pose Landmarker 的人物检测功能，
 * 用于在视频弹幕系统中实现人物避让效果。
 * 
 * 核心功能：
 * 1. 初始化 MediaPipe 模型（带超时保护）
 * 2. 检测视频帧中的人物姿态（33个关键点）
 * 3. 将关键点转换为避让区域（矩形边界框）
 * 4. 智能扩展头部区域（覆盖头发、帽子等）
 * 
 * 技术特点：
 * - 使用 Google MediaPipe Pose Landmarker Lite 模型
 * - GPU 加速（如果可用）
 * - 异步初始化，不阻塞主线程
 * - 失败时静默降级（不抛出错误）
 * - 关键点置信度过滤（可见度 > 0.5）
 * 
 * 性能优化：
 * - 使用 CDN 加载 WASM 和模型文件
 * - 缓存检测结果（在外层管理）
 * - 过滤低质量检测结果
 * 
 * 使用示例：
 * ```typescript
 * const service = new PoseDetectionService()
 * await service.initialize({ maxPersons: 2 })
 * 
 * const result = await service.detect(videoElement, Date.now())
 * console.log('检测到的人物区域:', result.areas)
 * ```
 * 
 * @module PoseDetectionService
 * @author Zhang Enyu
 * @date 2025
 */

import {
  PoseLandmarker,
  FilesetResolver,
} from '@mediapipe/tasks-vision'
import type { PoseLandmarkerResult } from '@mediapipe/tasks-vision'
import { DEFAULT_POSE_DETECTION_CONFIG, type PoseDetectionConfig } from '../../config/mediapipe.ts'
import type { DetectionArea } from '../../types'

/**
 * 人体姿态检测状态接口
 * 用于跟踪 MediaPipe 初始化和运行状态
 */
export interface PoseDetectionState {
  /** 是否已完成初始化 */
  initialized: boolean
  /** 是否正在初始化中 */
  initializing: boolean
  /** 初始化错误信息（如果有） */
  error?: Error
  /** 模型是否已加载 */
  modelLoaded: boolean
  /** 运行模式：视频流或单张图片 */
  runningMode: 'VIDEO' | 'IMAGE'
}

/**
 * 人体姿态检测结果接口
 * 包含时间戳和检测到的人物区域
 */
export interface PoseDetectionResult {
  /** 检测时间戳（毫秒） */
  timestamp: number
  /** 检测到的人物区域数组 */
  areas: DetectionArea[]
  /** MediaPipe 原始检测结果（可选） */
  raw?: PoseLandmarkerResult
}

/**
 * MediaPipe 人体姿态检测服务
 * 
 * 功能：
 * - 加载和初始化 MediaPipe Pose Landmarker 模型
 * - 在视频流中检测人物姿态关键点
 * - 将关键点转换为避让区域
 * - 提供超时保护和错误处理
 * 
 * 使用示例：
 * ```typescript
 * const service = new PoseDetectionService()
 * await service.initialize()
 * const result = await service.detect(videoElement, Date.now())
 * ```
 */
export class PoseDetectionService {
  /** MediaPipe 姿态检测器实例 */
  private landmarker: PoseLandmarker | null = null
  
  /** 服务状态 */
  private state: PoseDetectionState = {
    initialized: false,
    initializing: false,
    modelLoaded: false,
    runningMode: 'VIDEO',
  }

  /** 检测配置 */
  private config: PoseDetectionConfig = { ...DEFAULT_POSE_DETECTION_CONFIG }

  /**
   * 初始化 MediaPipe 姿态检测服务
   * 
   * 特性：
   * - 防止重复初始化
   * - 10秒超时保护
   * - 失败时静默降级（不抛出错误）
   * - 异步加载不阻塞主线程
   * 
   * @param config 可选的检测配置
   * @returns Promise<void>
   */
  async initialize(config?: PoseDetectionConfig): Promise<void> {
    // 防止重复初始化
    if (this.state.initialized) return
    if (this.state.initializing) {
      // 如果正在初始化，直接返回，避免重复请求
      return
    }

    this.state.initializing = true
    
    // 设置超时保护（10秒），防止初始化卡死
    const timeoutId = setTimeout(() => {
      if (this.state.initializing) {
        this.state.initializing = false
        this.state.error = new Error('初始化超时')
      }
    }, 10000)

    try {
      // 合并用户配置
      if (config) {
        this.config = { ...this.config, ...config }
      }
      
      // 使用 Promise.race 添加超时控制（8秒）
      const initPromise = this.initializeMediaPipe()
      const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error('初始化超时')), 8000)
      })
      
      // 竞速：哪个先完成就用哪个
      await Promise.race([initPromise, timeoutPromise])
      
      // 标记初始化成功
      this.state.initialized = true
      this.state.modelLoaded = true
    } catch (error) {
      // 静默失败：不抛出错误，允许降级到模拟检测
      this.state.error = error instanceof Error ? error : new Error('Pose detection initialize failed')
    } finally {
      // 清理超时定时器，重置状态
      clearTimeout(timeoutId)
      this.state.initializing = false
    }
  }

  /**
   * 初始化 MediaPipe 核心组件
   * 
   * 步骤：
   * 1. 加载 WASM 文件（vision tasks）
   * 2. 创建 PoseLandmarker 实例
   * 3. 配置检测参数
   * 
   * 使用 CDN 加载以下资源：
   * - WASM 文件：jsDelivr CDN
   * - 模型文件：Google Cloud Storage
   */
  private async initializeMediaPipe(): Promise<void> {
    // 第一步：加载 WASM 运行时文件
    const filesetResolver = await FilesetResolver.forVisionTasks(
      'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14/wasm'
    )

    // 第二步：创建姿态检测器实例
    this.landmarker = await PoseLandmarker.createFromOptions(filesetResolver, {
      baseOptions: {
        // 使用轻量级模型（lite版本，float16精度）
        modelAssetPath: 'https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_lite/float16/1/pose_landmarker_lite.task',
        delegate: 'GPU' // 优先使用 GPU 加速
      },
      // 视频流模式
      runningMode: this.config.runningMode || 'VIDEO',
      // 最多检测的人数
      numPoses: this.config.maxPersons,
      // 姿态检测最低置信度（0-1）
      minPoseDetectionConfidence: this.config.minPoseDetectionConfidence,
      // 姿态存在最低置信度（0-1）
      minPosePresenceConfidence: this.config.minPosePresenceConfidence,
      // 关键点跟踪最低置信度（0-1）
      minTrackingConfidence: this.config.minTrackingConfidence,
    })
  }

  /**
   * 获取当前服务状态
   * @returns 状态对象的副本
   */
  getState(): PoseDetectionState {
    return { ...this.state }
  }

  /**
   * 检测视频帧中的人物姿态
   * 
   * 流程：
   * 1. 检查服务是否已初始化
   * 2. 验证视频元素是否就绪
   * 3. 调用 MediaPipe 进行检测
   * 4. 将关键点转换为避让区域
   * 
   * @param video HTML 视频元素
   * @param timestamp 时间戳（毫秒）
   * @returns 检测结果（包含人物区域）
   * @throws 如果服务未初始化
   */
  async detect(video: HTMLVideoElement, timestamp: number): Promise<PoseDetectionResult> {
    if (!this.landmarker) {
      throw new Error('PoseDetectionService not initialized')
    }

    // 严格检查视频是否就绪
    // readyState >= 2 表示有足够的数据可以播放
    if (!video || video.readyState < 2 || video.videoWidth === 0 || video.videoHeight === 0) {
      // 视频未准备好，返回空结果（不抛出错误）
      return {
        timestamp,
        areas: []
      }
    }

    // 调用 MediaPipe 检测视频帧
    const result = await this.landmarker.detectForVideo(video, timestamp)
    
    // 将关键点转换为矩形避让区域
    const areas = this.extractDetectionAreas(result, video.videoWidth, video.videoHeight)

    return {
      timestamp,
      areas,
      raw: result,
    }
  }

  /**
   * 从 MediaPipe 检测结果中提取人物避让区域
   * 
   * 算法流程：
   * 1. 过滤高置信度关键点（可见度 > 0.5）
   * 2. 智能扩展头部区域（头部关键点 0-10）
   * 3. 计算包围所有关键点的边界框
   * 4. 过滤掉过小的区域
   * 
   * MediaPipe Pose 关键点索引：
   * - 0-10: 头部（鼻子、眼睛、耳朵、嘴巴）
   * - 11-16: 躯干（肩膀、臀部）
   * - 17-22: 手臂（肘部、手腕）
   * - 23-32: 腿部（膝盖、脚踝、脚）
   * 
   * @param result MediaPipe 检测结果
   * @param width 视频宽度（像素）
   * @param height 视频高度（像素）
   * @returns 检测区域数组
   */
  private extractDetectionAreas(result: PoseLandmarkerResult, width: number, height: number): DetectionArea[] {
    const areas: DetectionArea[] = []

    // 边界检查
    if (!result.landmarks || result.landmarks.length === 0 || width === 0 || height === 0) {
      return areas
    }

    // 置信度阈值：只使用可见度高于 0.5 的关键点
    const VISIBILITY_THRESHOLD = 0.5

    // 遍历每个检测到的人物
    result.landmarks.forEach(landmarks => {
      // 第一步：提取关键点坐标，同时记录可见度
      const points = landmarks.map((point, index) => ({
        x: point.x * width,        // 归一化坐标转为像素坐标
        y: point.y * height,
        visibility: point.visibility || 0,  // 可见度（0-1）
        index                       // 关键点索引
      }))

      // 第二步：过滤出高置信度的关键点
      const highConfidencePoints = points.filter(p => 
        p.visibility > VISIBILITY_THRESHOLD &&  // 置信度足够高
        p.x >= 0 &&                             // 在画面内
        p.y >= 0 &&
        p.x <= width &&
        p.y <= height
      )

      // 如果高质量关键点太少（< 10个），跳过此人物
      if (highConfidencePoints.length < 10) {
        return
      }

      // 第三步：使用所有高置信度的关键点
      // 这样可以包含手部、脚部等细节，提高避让精度
      let keyPoints = highConfidencePoints.map(p => ({
        x: p.x,
        y: p.y,
        index: p.index
      }))

      if (keyPoints.length === 0) return

      // 🎯 第四步：头部智能扩展算法
      // 
      // 背景：MediaPipe 只检测面部关键点（鼻子、眼睛、耳朵、嘴巴）
      // 问题：无法覆盖头发、帽子、耳环等头部装饰
      // 解决：在头部周围添加虚拟扩展点，扩大避让范围
      
      // 头部关键点：0-10（鼻子、眼睛、耳朵、嘴巴）
      const headPoints = keyPoints.filter(p => p.index <= 10)
      
      if (headPoints.length > 0) {
        // 4.1 计算头部中心点（通常是鼻子或眼睛中心）
        const headCenterX = headPoints.reduce((sum, p) => sum + p.x, 0) / headPoints.length
        const headCenterY = headPoints.reduce((sum, p) => sum + p.y, 0) / headPoints.length
        
        // 4.2 估算头部半径（从中心到最远点的距离）
        const headRadius = Math.max(
          ...headPoints.map(p => 
            Math.sqrt((p.x - headCenterX) ** 2 + (p.y - headCenterY) ** 2)
          )
        )
        
        // 4.3 在头部周围添加虚拟扩展点（8个方向）
        // 扩展系数：头部半径 × 1.6 + 额外 25px
        // 目的：覆盖头发、帽子、耳环等头部装饰
        const expandRadius = headRadius * 1.6 + 25
        const angles = [0, 45, 90, 135, 180, 225, 270, 315] // 8个方向
        
        // 在 8 个方向上添加虚拟点
        angles.forEach(angle => {
          const rad = (angle * Math.PI) / 180  // 角度转弧度
          keyPoints.push({
            x: headCenterX + Math.cos(rad) * expandRadius,
            y: headCenterY + Math.sin(rad) * expandRadius,
            index: -1 // 标记为虚拟点（非真实关键点）
          })
        })
      }

      // 第五步：计算包围所有关键点的边界框
      const xs = keyPoints.map(p => p.x)
      const ys = keyPoints.map(p => p.y)

      // 边界扩展 20px（头部已特殊处理，这里只需小幅扩展）
      const minX = Math.max(Math.min(...xs) - 20, 0)        // 左边界
      const maxX = Math.min(Math.max(...xs) + 20, width)    // 右边界
      const minY = Math.max(Math.min(...ys) - 20, 0)        // 上边界
      const maxY = Math.min(Math.max(...ys) + 20, height)   // 下边界

      // 第六步：过滤掉过小的区域（可能是误检）
      // 最小尺寸 = 视频短边的 5%
      const minSize = Math.min(width, height) * 0.05
      if (maxX - minX < minSize || maxY - minY < minSize) {
        return
      }

      // 第七步：添加到结果数组
      areas.push({
        left: minX,
        right: maxX,
        top: minY,
        bottom: maxY,
        keyPoints // 保存关键点，供后续精确碰撞检测使用
      } as any)
    })

    return areas
  }

  /**
   * 关闭检测服务并释放资源
   * 
   * 操作：
   * - 关闭 MediaPipe 实例
   * - 重置状态
   * - 清理内存
   */
  async close(): Promise<void> {
    if (this.landmarker) {
      this.landmarker.close()
      this.landmarker = null
    }
    
    // 重置状态
    this.state = {
      initialized: false,
      initializing: false,
      modelLoaded: false,
      runningMode: 'VIDEO',
    }
  }
}